﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading;
using System.Collections.Specialized;
using System.IO;
using Musicman.Common.Entities;
using Musicman.Common;

namespace Musicman.Client2.Components
{
    public partial class FileDownloader : Component
    {
        public const int MAX_THREADS = 5;
        
        private const int buffersize = 1024 * 16;
        private AutoResetEvent workerThreadsAre = new AutoResetEvent(false);        
        private HybridDictionary userStateToLifetime = new HybridDictionary();
        private long numWorkingThreads = 0;
        public long TotalCount { get; set; }
        public long CurrentCount { get; set; }
        public long FilesCount
        {
            get
            {
                return userStateToLifetime.Count;
            }
        }

        public event ProgressChangedEventHandler DownloadProgressChanged;
        public event AsyncCompletedEventHandler DownloadCompleted;
        public event EventHandler FileCountChanged;

        public FileDownloader()
        {
            InitializeComponent();
        }

        public FileDownloader(IContainer container)
        {
            container.Add(this);
            InitializeComponent();
        }

        private bool Canceled(object taskId)
        {
            return (userStateToLifetime[taskId] == null);
        }

        public void Cancel(object taskId)
        {
            lock (userStateToLifetime.SyncRoot)
            {
                if (userStateToLifetime.Contains(taskId))
                {
                    CurrentCount += (userStateToLifetime[taskId] as DownloadFileArgs).SearchFile.Length;
                }
                userStateToLifetime.Remove(taskId);
            }
            DownloadFileCompletedArgs completedArgs = new DownloadFileCompletedArgs(null, true, taskId, null);
            OnCompleted(completedArgs);
            OnFileCountChanged();
        }

        public void CancelAll()
        {
            userStateToLifetime.Clear();
        }

        private void DownloadLocal(object arg)
        {
            while (Interlocked.Read(ref numWorkingThreads) >= MAX_THREADS)
            {
                workerThreadsAre.WaitOne();
            }
            Interlocked.Increment(ref numWorkingThreads);
            DownloadFileArgs e = arg as DownloadFileArgs;
            FileStream fsSource = null;
            FileStream fsDest = null;
            try
            {
                fsSource = File.Open(String.Format(@"{0}\{1}", e.SearchFile.FilePath, e.SearchFile.Name), 
                    FileMode.Open, FileAccess.Read);
                fsDest = File.Open(String.Format(@"{0}\{1}", e.DestFolder, e.SearchFile.Name),
                    FileMode.OpenOrCreate, FileAccess.Write);
                byte[] buffer = new byte[buffersize];

                long totalLength = fsSource.Length;
                
                int count = fsSource.Read(buffer, 0, buffersize);
                long finished = count;
                CurrentCount += count;
                while (count > 0 && !Canceled(e.UserSuppliedState))
                {
                    fsDest.Write(buffer, 0, count);
                    count = fsSource.Read(buffer, 0, buffersize);
                    
                    finished = finished + count;
                    CurrentCount += count;
                    
                    float percentage = (float)finished / (float)totalLength * 100f;
                    float overallPercentage = (float)CurrentCount / (float)TotalCount * 100f;
                    DownloadProgressChangedEventArgs pcArgs = new DownloadProgressChangedEventArgs(
                        (int)percentage, (int)overallPercentage, e.UserSuppliedState);
                    OnProgressChanged(pcArgs);
                }
                fsSource.Close();
                fsDest.Close();
                if (!Canceled(e.UserSuppliedState))
                {
                    lock (userStateToLifetime.SyncRoot)
                    {
                        userStateToLifetime.Remove(e.UserSuppliedState);
                    }
                    DownloadFileCompletedArgs completedArgs = new DownloadFileCompletedArgs(null, false, e.UserSuppliedState, e.SearchFile);
                    OnCompleted(completedArgs);
                }
                else
                {
                    CurrentCount -= finished;
                    DownloadFileCompletedArgs completedArgs = new DownloadFileCompletedArgs(null, false, e.UserSuppliedState, null);
                    OnCompleted(completedArgs);
                }
                OnFileCountChanged();
            }
            catch (Exception ex)
            {
                if (fsSource != null) fsSource.Close();
                if (fsDest != null) fsDest.Close();
                LogManager.LogException("Erorr copying files", ex);
                Cancel(e.UserSuppliedState);
            }
            Interlocked.Decrement(ref numWorkingThreads);
            workerThreadsAre.Set();
        }

        private void DownloadRemote(object arg)
        {
            while (Interlocked.Read(ref numWorkingThreads) >= MAX_THREADS)
            {
                workerThreadsAre.WaitOne();
            }
            Interlocked.Increment(ref numWorkingThreads);
            // copy file from remote resource
            Interlocked.Decrement(ref numWorkingThreads);
            workerThreadsAre.Set();
        }

        public void AddDownloads(IEnumerable<SearchFile> files, string downloadLocation)
        {
            WaitCallback localWcb = new WaitCallback(DownloadLocal);
            WaitCallback remoteWcb = new WaitCallback(DownloadRemote);
            foreach (SearchFile sf in files)
            {
                Guid taskID = Guid.NewGuid();
                DownloadFileArgs args = new DownloadFileArgs(sf, downloadLocation, taskID);
                lock (userStateToLifetime.SyncRoot)
                {
                    if (userStateToLifetime.Count == 0)
                    {
                        CurrentCount = 0;
                        TotalCount = 0;
                    }
                    userStateToLifetime.Add(taskID, args);
                    TotalCount += sf.Length;
                }
                if (File.Exists(String.Format(@"{0}\{1}", sf.FilePath, sf.Name)))
                {
                    ThreadPool.QueueUserWorkItem(localWcb, args);
                }
                else
                {
                    // for the future release
                    // ThreadPool.QueueUserWorkItem(remoteWcb, args);
                }
                OnFileCountChanged();
            }
        }

        private void OnProgressChanged(DownloadProgressChangedEventArgs e)
        {
            if (this.DownloadProgressChanged != null)
            {
                DownloadProgressChanged(this, e);
            }
        }

        private void OnCompleted(AsyncCompletedEventArgs e)
        {
            if (this.DownloadCompleted != null)
            {
                DownloadCompleted(this, e);
            }
        }

        private void OnFileCountChanged()
        {
            if (this.FileCountChanged != null)
            {
                DownloadFileArgs[] files = new DownloadFileArgs[userStateToLifetime.Count];
                userStateToLifetime.Values.CopyTo(files, 0);
                FileCountChanged(files, null);
            }
        }
        

    }

    public class DownloadProgressChangedEventArgs : ProgressChangedEventArgs
    {
        private int overallPercentage;
        public DownloadProgressChangedEventArgs(int percentage,
                        int overallPercentage, object userSuppliedState)
            : base(percentage, userSuppliedState)
        {
            this.overallPercentage = overallPercentage;
        }
        public int OverallPercentage
        {
            get { return overallPercentage; }
            set { overallPercentage = value; }
        }
    }

    public class DownloadFileArgs
    {
        public object UserSuppliedState { get; set; }
        public SearchFile SearchFile { get; set; }
        public string DestFolder { get; set; }

        public DownloadFileArgs(SearchFile searchFile, string destFolder, object userSuppliedState)
        {
            SearchFile = searchFile;
            DestFolder = destFolder;
            UserSuppliedState = userSuppliedState;
        }
    }

    public class DownloadFileCompletedArgs : AsyncCompletedEventArgs
    {
        public SearchFile SFile { get; private set; }
        public DownloadFileCompletedArgs(Exception error, bool canceled, object userState, SearchFile searchFile)
            :base(error, canceled, userState)
        {
            SFile = searchFile;
        }
    }
}
